//
//  PYRNUpdate.m
//  IHome
//
//  Created by 朱吉峥 on 2020/2/6.
//  Copyright © 2020 com.zhujizheng. All rights reserved.
//

#import "PYRNUpdate.h"
#import "SSZipArchive.h"
#import "PYBsdiff.h"
#import "PYRNBridge.h"

NSString *const RCTJavaScriptDidCatchExceptionNotification = @"RCTJavaScriptDidCatchExceptionNotification";

// key
static NSString *const kRNPackageVersion = @"packageVersion";
static NSString *const kRNBundleVersion = @"bundleVersion";
static NSString *const kRNBundlePatchUrl = @"patchUrl";
static NSString *const kRNBundleCommonUrl = @"commonUrl";
static NSString *const kRNRollback = @"kRNRollback";
// file name
static NSString *const BUNDLE_DIR_NAME = @"bundle";
static NSString *const BUNDLE_ZIP_NAME = @"bundle.zip";
static NSString *const BUNDLE_FILE_NAME = @"main.jsbundle";
static NSString *const COMMON_DIR_NAME = @"common";
static NSString *const COMMON_ZIP_NAME = @"common.zip";

@interface PYRNUpdate ()

@property (nonatomic, copy) NSString *bundleVersion;

@end

@implementation PYRNUpdate

#pragma mark - 外部方法
+ (NSURL *)binaryBundleURL
{
    return [[PYRNUpdate sharedInstance] binaryBundleURL];
}
+ (NSURL *)bundleURL
{
    return [[PYRNUpdate sharedInstance] bundleURL];
}
+ (void)checkRNUpdate
{
    return [[PYRNUpdate sharedInstance] checkRNUpdate];
}


+ (instancetype)sharedInstance
{
    static PYRNUpdate *update = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        update = [[PYRNUpdate alloc] init];
        [update addNotification];
    });
    return update;
}

- (void)addNotification
{
    // 监听jsbundle文件加载失败
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(failToLoadNotification:) name:RCTJavaScriptDidFailToLoadNotification object:nil];
    // 监听js执行错误引发的崩溃
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(catchExceptionNotification:) name:RCTJavaScriptDidCatchExceptionNotification object:nil];
}

- (void)catchExceptionNotification:(NSNotification *)notify
{
    NSObject *obj = [notify object];
    NSDictionary *userInfo = [notify userInfo];
    NSLog(@"obj = %@, userInfo = %@", obj, userInfo);
    NSLog(@"加载jsbundle崩溃了，需要回滚");
    [self rollbackToPreversion];
}

- (void)failToLoadNotification:(NSNotification *)notify
{
    NSObject *obj = [notify object];
    NSDictionary *userInfo = [notify userInfo];
    NSLog(@"obj = %@, userInfo = %@", obj, userInfo);
    NSLog(@"加载jsbundle出现问题，需要回滚");
    // rollback
    [self rollbackToPreversion];
}

- (void)rollbackToPreversion
{
    if (self.bundleVersion) {
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
        NSArray *rollbackArr = [defaults objectForKey:kRNRollback];
        NSMutableArray *mRollbackArr = [NSMutableArray arrayWithArray:rollbackArr];
        [mRollbackArr addObject:self.bundleVersion];
        [defaults setObject:mRollbackArr forKey:kRNRollback];
        [defaults synchronize];
    }

    [PYRNBridge reload];
}

- (NSURL *)bundleURL
{
    NSString *packageVersion = [self packageVersion];
    NSArray *fileNameArr = [PYFileManager getAllFileNamesFromDocumentDirectory:[NSString stringWithFormat:@"%@/%@", path_Download_RNupdate_Directory, packageVersion]];
    NSMutableArray *mFileNameArr = [NSMutableArray arrayWithArray:fileNameArr];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSArray *rollbackArr = [defaults objectForKey:kRNRollback];
    NSLog(@"rollbackArr = %@", rollbackArr);
    // 移除需要回滚的版本
    [mFileNameArr removeObjectsInArray:rollbackArr];
    // 从大到小排序
    [mFileNameArr sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        if ([obj1 integerValue] > [obj2 integerValue]) {
            return (NSComparisonResult)NSOrderedAscending;
        } else if ([obj1 integerValue] < [obj2 integerValue]) {
            return (NSComparisonResult)NSOrderedDescending;
        } else {
            return (NSComparisonResult)NSOrderedSame;
        }
    }];
    NSString *bundleVersion = [mFileNameArr firstObject];
    if (!bundleVersion || [bundleVersion isEqualToString:COMMON_ZIP_NAME]) {
        [self checkRNUpdate];
        return [self binaryBundleURL];
    }
    NSString *downloadDir = [self downloadDir];
    NSString *bundlePath = [[[[downloadDir stringByAppendingPathComponent:packageVersion] stringByAppendingPathComponent:bundleVersion] stringByAppendingPathComponent:BUNDLE_DIR_NAME] stringByAppendingPathComponent:BUNDLE_FILE_NAME];
    if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:NULL]) {
        NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
        NSLog(@"RN文件使用自定义路径：%@", bundleURL);
        self.bundleVersion = bundleVersion;
        return bundleURL;
    }
    [self checkRNUpdate];
    return [self binaryBundleURL];
}

- (NSString *)downloadDir
{
//    NSString *directory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject];
//    NSString *downloadDir = [directory stringByAppendingPathComponent:@"RNupdate"];
    NSString *downloadDir = [PYFileManager getOrCreateDirectoryInDocumentWithSubPath:path_Download_RNupdate_Directory];
    return downloadDir;
}

- (NSURL *)binaryBundleURL
{
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
    return url;
}

- (NSString *)packageVersion
{
    static NSString *version = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
        version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
    });
    return version;
}

- (void)checkRNUpdate
{
    NSLog(@"开始检查RN更新");
    NSString *packageVersion = [[self packageVersion] stringByReplacingOccurrencesOfString:@"." withString:@"_"];
    NSString *rnUpdateUrl = [NSString stringWithFormat:@"%@/rnupdate/select/%@", ip_address, packageVersion];
    [PYHttp requestIHomeServerWithURLString:rnUpdateUrl parameters:nil requestType:PY_GET success:^(id  _Nullable data) {
        if ([data isKindOfClass:[NSDictionary class]]) {
            NSDictionary *dict = [NSDictionary dictionaryWithDictionary:data];

            // 判断包版本是否正确
            NSString *curPackageVersion = [self packageVersion];
            NSLog(@"curPackageVersion = %@", curPackageVersion);
            NSString *packageVersion = [dict objectForKey:kRNPackageVersion];
            NSLog(@"packageVersion = %@", curPackageVersion);
            // 如果包版本不正确，直接return
            if (![curPackageVersion isEqualToString:packageVersion]) return ;

            NSString *patchUrl = [dict objectForKey:kRNBundlePatchUrl];
            NSString *bundleVersion = [NSString stringWithFormat:@"%ld", (long)[dict integerValueForKey:kRNBundleVersion default:0]];
            NSLog(@"RN文件下载地址：%@", patchUrl);
            
            /* 补丁文件保存路径 */
            NSString *bundleFileDir = [PYFileManager getOrCreateDirectoryInDocumentWithSubPath:[NSString stringWithFormat:@"%@/%@/%@", path_Download_RNupdate_Directory, packageVersion, bundleVersion]];
            NSString *patchName = [NSString stringWithFormat:@"%@.patch", bundleVersion];
            NSString *filePath = [bundleFileDir stringByAppendingPathComponent:patchName];
            if ([PYFileManager isExistForPath:filePath]) {
                NSLog(@"此RN文件路径已存在：%@", filePath);
                return;
            }
            NSString *commonUrl = [dict objectForKey:kRNBundleCommonUrl];
            
            [PYHttp downloadFileWithUrl:patchUrl path:filePath progress:nil completion:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
                if (error) {
                    NSLog(@"下载RN文件失败：%@", error);
                } else {
                    NSLog(@"下载RN文件成功");
                    self.bundleVersion = bundleVersion;
                    [self clearDownloadDir];
                    [self downloadCommonFileIfNeeded:commonUrl block:^{
                        [self mergeBundle];
                        [self unzipFile];
                    }];
                }
            }];
        }
    } failure:nil];
}

- (void)clearDownloadDir
{
    // 只保留最新版本前5个版本的jsbundle文件
    NSArray *fileNames = [PYFileManager getAllFileNamesFromDocumentDirectory:path_Download_RNupdate_Directory];
    NSString *packageVersion = [self packageVersion];
    // 删除其他版本的文件夹
    for (NSString *dirName in fileNames) {
        if ([dirName isEqualToString:packageVersion]) {
            continue;
        }
        [PYFileManager deleteByName:dirName fromDocumentDirectory:path_Download_RNupdate_Directory];
    }
    NSString *packageVersionDir = [path_Download_RNupdate_Directory stringByAppendingPathComponent:packageVersion];
    NSArray *packageVersionFileNames = [PYFileManager getAllFileNamesFromDocumentDirectory:packageVersionDir];
    // 删除超过5个版本的文件
    for (NSString *fileName in packageVersionFileNames) {
        if (self.bundleVersion.integerValue - fileName.integerValue > 5) {
            [PYFileManager deleteByName:fileName fromDocumentDirectory:packageVersionDir];
        }
    }
}

- (BOOL)unzipFile
{
    /* zip文件保存路径 */
    NSString *bundleFileDir = [PYFileManager getOrCreateDirectoryInDocumentWithSubPath:[NSString stringWithFormat:@"%@/%@/%@", path_Download_RNupdate_Directory, [self packageVersion], self.bundleVersion]];
    NSString *filePath = [bundleFileDir stringByAppendingPathComponent:BUNDLE_ZIP_NAME];
    /* 解压目标路径 */
    NSString *destinationPath = bundleFileDir;
    BOOL isSuccess = [SSZipArchive unzipFileAtPath:filePath toDestination:destinationPath];
    if (isSuccess) {
        NSLog(@"解压成功");
        [PYRNBridge reload];
    } else {
        NSLog(@"解压失败");
    }
    return isSuccess;
}

- (void)mergeBundle
{
    /* 通用zip文件保存路径 */
    NSString *packageVersionDir = [PYFileManager getOrCreateDirectoryInDocumentWithSubPath:[NSString stringWithFormat:@"%@/%@", path_Download_RNupdate_Directory, [self packageVersion]]];
    NSString *commonZipPath = [packageVersionDir stringByAppendingPathComponent:COMMON_ZIP_NAME];
    NSData *commonZipData = [NSData dataWithContentsOfFile:commonZipPath];
    NSLog(@"commonZipData(基础包大小)___%tu", commonZipData.length);
    
    /* 补丁文件保存路径 */
    NSString *bundleVersionDir = [PYFileManager getOrCreateDirectoryInDocumentWithSubPath:[NSString stringWithFormat:@"%@/%@/%@", path_Download_RNupdate_Directory, [self packageVersion], self.bundleVersion]];
    NSString *patchName = [NSString stringWithFormat:@"%@.patch", self.bundleVersion];
    NSString *patchPath = [bundleVersionDir stringByAppendingPathComponent:patchName];
    NSData *patchData = [NSData dataWithContentsOfFile:patchPath];
    NSLog(@"patchData(补丁包大小)___%tu", patchData.length);
    
    NSString *newFilePath = [bundleVersionDir stringByAppendingPathComponent:BUNDLE_ZIP_NAME];
    [self bspatchWithOldPath:commonZipPath newPath:newFilePath patchPath:patchPath];
    NSData *newFileData = [NSData dataWithContentsOfFile:newFilePath];
    NSLog(@"newFileData(合成后大小)___%tu", newFileData.length);
}

- (void)bspatchWithOldPath:(NSString *)oldFilePath newPath:(NSString *)newPath patchPath:(NSString *)patchPath
{
    const char *argv[4];
    argv[0] = "bspatch";
    // oldPath
    argv[1] = [oldFilePath UTF8String];
    // newPath
    argv[2] = [newPath UTF8String];
    // patchPath
    argv[3] = [patchPath UTF8String];
    BsdiffUntils_bspatch(4, argv);
}

- (void)downloadCommonFileIfNeeded:(NSString *)commonUrl block:(void(^)(void))block
{
    /* 通用文件保存路径 */
    NSString *packageVersionDir = [PYFileManager getOrCreateDirectoryInDocumentWithSubPath:[NSString stringWithFormat:@"%@/%@", path_Download_RNupdate_Directory, [self packageVersion]]];
    NSString *commonZipPath = [packageVersionDir stringByAppendingPathComponent:COMMON_ZIP_NAME];
    if ([PYFileManager isExistForPath:commonZipPath]) {
        NSLog(@"通用文件已存在");
        if (block) block();
        return;
    }
    NSLog(@"通用文件不存在，需要下载");
    [PYHttp downloadFileWithUrl:commonUrl path:commonZipPath progress:nil completion:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        if (error) {
            NSLog(@"下载RN文件失败：%@", error);
        } else {
            NSLog(@"下载RN文件成功");
            if (block) block();
        }
    }];
}

@end
